﻿using AutoMapper;
using Mes.Application.DTO.Inputs.Operations;
using Mes.Application.DTO.Inputs.WorkOrders;
using Mes.Application.DTO.Results.Operations;
using Mes.Application.DTO.Results.WorkOrders;
using Mes.Domain.BoundedContext;
using Mes.Domain.BoundedContext.Entities.Operations;
using Mes.Domain.BoundedContext.Entities.WorkOrders;
using Mes.Infrastructure.Data.BoundedContext.Configurations;
using Mes.Infrastructure.Data.BoundedContext.Extensions;
using Mes.Infrastructure.Data.BoundedContext.Repositories.Operations;
using Mes.Infrastructure.Data.BoundedContext.Repositories.WorkOrders;
using Mes.Application.Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Mes.Application.Common.Resource;
using Mes.Infrastructure.Data.BoundedContext.Repositories.MasterData;

namespace Mes.Application.Services.WorkOrders
{
    public class WoService : IWoService
    {
        private readonly IMapper _mapper;
        private readonly IWoRepository _woRepository;
        private readonly IMaterialRepository _materialRepository;
        private readonly IProcessRepository _processRepository;
        private readonly IItemProcessLinkRepository _itemProcessLinkRepository;
        private readonly IMaterialMaterialBOMLinkRepository _materialMaterialBOMLinkRepository;
        private readonly IMaterialBOMRepository _materialBOMRepository;
        private readonly BuiltInTypesConfiguration _builtInTypes;
        private readonly IJobRepository _jobRepository;
        private readonly IJobRouteRepository _jobRouteRepository;
        private readonly IProductionUnitRepository _productionUnitRepository;
        private readonly IOperRepository _operRepository;
        private readonly IOperEntLinkRepository _operEntLinkRepository;
        private readonly IOperStepGrpRepository _operStepGrpRepository;
        private readonly IOperStepRepository _operStepRepository;
        private readonly IOperEntRouteRepository _operEntRouteRepository;
        private readonly IBomItemOperLinkRepository _bomItemOperLinkRepository;
        private readonly IBomItemOperStepLinkRepository _bomItemOperStepLinkRepository;
        private readonly IJobStepGrpRepository _jobStepGrpRepository;
        private readonly IJobStepRepository _jobStepRepository;
        private readonly IJobBOMRepository _jobBOMRepository;
        private readonly IJobStateRepository _job_StateRepository;
        private readonly IJobBomStepRepository _jobStepBomRepository;

        public WoService(IMapper mapper, IWoRepository woRepository, IMaterialRepository materialRepository, IProcessRepository processRepository, IItemProcessLinkRepository itemProcessLinkRepository,
            IMaterialMaterialBOMLinkRepository materialMaterialBOMLinkRepository, IMaterialBOMRepository materialBOMRepository, IOptions<BuiltInTypesConfiguration> options, IJobRepository jobRepository,
            IJobRouteRepository jobRouteRepository, IProductionUnitRepository productionUnitRepository, IOperRepository operRepository, IOperEntLinkRepository operEntLinkRepository,
            IOperStepGrpRepository operStepGrpRepository, IOperStepRepository operStepRepository, IOperEntRouteRepository operEntRouteRepository, IBomItemOperLinkRepository bomItemOperLinkRepository,
            IJobStepGrpRepository jobStepGrpRepository, IJobStepRepository jobStepRepository, IJobBOMRepository jobBOMRepository, IJobStateRepository job_StateRepository, IBomItemOperStepLinkRepository bomItemOperStepLinkRepository,
            IJobBomStepRepository jobStepBomRepository)
        {
            if (mapper == null || woRepository == null || materialRepository == null || processRepository == null || itemProcessLinkRepository == null
                || materialMaterialBOMLinkRepository == null || materialBOMRepository == null || options == null || jobRepository == null || jobRouteRepository == null
                || productionUnitRepository == null || operRepository == null || operEntLinkRepository == null || operStepGrpRepository == null || operStepRepository == null
                || operEntRouteRepository == null || bomItemOperLinkRepository == null || jobStepGrpRepository == null || jobStepRepository == null || jobBOMRepository == null || job_StateRepository == null
                || bomItemOperStepLinkRepository == null || jobStepBomRepository == null)
            {
                throw new ArgumentNullException();
            }
            _mapper = mapper;
            _woRepository = woRepository;
            _materialRepository = materialRepository;
            _processRepository = processRepository;
            _itemProcessLinkRepository = itemProcessLinkRepository;
            _materialMaterialBOMLinkRepository = materialMaterialBOMLinkRepository;
            _materialBOMRepository = materialBOMRepository;
            _builtInTypes = options.Value;
            _jobRepository = jobRepository;
            _jobRouteRepository = jobRouteRepository;
            _productionUnitRepository = productionUnitRepository;
            _operRepository = operRepository;
            _operEntLinkRepository = operEntLinkRepository;
            _operStepGrpRepository = operStepGrpRepository;
            _operStepRepository = operStepRepository;
            _operEntRouteRepository = operEntRouteRepository;
            _bomItemOperLinkRepository = bomItemOperLinkRepository;
            _jobStepGrpRepository = jobStepGrpRepository;
            _jobStepRepository = jobStepRepository;
            _jobBOMRepository = jobBOMRepository;
            _job_StateRepository = job_StateRepository;
            _bomItemOperStepLinkRepository = bomItemOperStepLinkRepository;
            _jobStepBomRepository = jobStepBomRepository;
        }

        /// <summary>
        /// 通过工艺新增工单，此接口会将工艺及其下的工序，以及工序下的BOM、Entity、StepGrp和StepGrp下的Step以及Step下的BOM转换为工单及其下作业和作业的一些关联表
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task AddWoByProcessAsync(WoInputWithProcess input)
        {
            // 验证输入的BOM是否有效
            if (!IsInputBomValid(input.ProcessId, input.BomId))
            {
                throw new CustomException(Messages.InputArgsIlleagl);
            }

            // BOM必须存在
            var bom = _materialBOMRepository.Find(x => x.Id == input.BomId);
            if (bom == null)
            {
                throw new CustomException(Messages.MaterialBOMNotFound);
            }

            // 工单的开工数量和所需产量默认为输入值
            double woStartQty = input.Start_Qty;
            double woReqQty = input.Req_Qty;

            var opers = await _operRepository.Query().Where(x => x.Process_Id == input.ProcessId).ToListAsync();
            int opersNums = opers.Count();
            if (opersNums > 0)
            {
                var jobs = new HashSet<Job>();
                var jobStepGroups = new HashSet<JobStepGrp>();
                var jobSteps = new HashSet<JobStep>();
                var jobRoutes = new HashSet<JobRoute>();
                var jobBoms = new HashSet<JobBom>();
                var jobStepBoms = new HashSet<JobBomStep>();
                // 当工单下存在作业时，工单的开工数量和所需产量分别为所有作业开工数量和所需产量的和
                woStartQty = input.Start_Qty * opersNums;
                woReqQty = input.Req_Qty * opersNums;
                foreach (var oper in opers)
                {
                    var operEntLink = _operEntLinkRepository.Query().Where(x => x.Process_Id == input.ProcessId && x.Oper_Id == oper.Oper_Id).ToListAsync();
                    if (operEntLink.Result.Count() != 1) // 一道工序只支持关联一个实体
                    {
                        throw new CustomException(Messages.EntityNotFound);
                    }
                    var operEnt = operEntLink.Result.First();
                    // 基于工艺下的工序生成工单下的作业
                    var job = new Job(input.Wo_Id, oper.Oper_Id, input.Item_Id, input.Start_Qty, input.Req_Qty, input?.Req_Finish_Time_Local, operEnt.Ent_Id, operEnt.Ent_Id,
                        oper.Oper_Desc, oper.First_Oper, oper.Final_Oper, oper.Display_Seq, operEnt.Est_Prod_Rate, operEnt.Est_Lab_Rate, operEnt.Est_Setup_Time, operEnt.Est_Teardown_Time,
                        operEnt.Est_Transfer_Time, oper.Oper_Cost, oper.Notes, 1, input.Wo_Priority);
                    if (job == null)
                    {
                        throw new CustomException(Messages.ErrorGeneratingJob);
                    }
                    jobs.Add(job);

                    var operStepGroups = _operStepGrpRepository.Query().Where(x => x.Process_Id == input.ProcessId && x.Oper_Id == oper.Oper_Id).ToArrayAsync();
                    if (operStepGroups.Result.Count() > 0)
                    {
                        foreach (var operStepGroup in operStepGroups.Result)
                        {
                            // 基于工艺下工序中的工步组生成工单下作业中的工步组
                            var jobStepGroup = new JobStepGrp(input.Wo_Id, oper.Oper_Id, operStepGroup.Step_Grp_Id, operStepGroup.Step_Grp_Desc, operStepGroup.Step_Grp_Seq);
                            if (jobStepGroup == null)
                            {
                                throw new CustomException(Messages.ErrorGeneratingJobStepGroup);
                            }
                            jobStepGroups.Add(jobStepGroup);
                        }
                    }

                    var operSteps = _operStepRepository.Query().Where(x => x.Process_Id == input.ProcessId && x.Oper_Id == oper.Oper_Id).ToArrayAsync();
                    if (operSteps.Result.Count() > 0)
                    {
                        foreach (var operStep in operSteps.Result)
                        {
                            // 基于工序中工步组下的工步生成作业中工步组下的工步
                            var jobStep = new JobStep(input.Wo_Id, oper.Oper_Id, operStep.Step_No, operStep.Step_Seq, operStep.Step_Name, operStep.Step_Desc, operStep.Step_Grp_Id);
                            if (jobStep == null)
                            {
                                throw new CustomException(Messages.ErrorGeneratingJobStep);
                            }
                            jobSteps.Add(jobStep);
                        }
                    }

                    var operEntRoutes = _operEntRouteRepository.Query().Where(x => x.Process_Id == input.ProcessId && x.Oper_Id == oper.Oper_Id).ToArrayAsync();
                    if (operEntRoutes.Result.Count() > 0)
                    {
                        foreach (var operEntRoute in operEntRoutes.Result)
                        {
                            // 基于工艺下工序的路由生成工单下作业的路由
                            var jobRoute = new JobRoute(input.Wo_Id, oper.Oper_Id, operEntRoute.Input_Oper_Id, operEntRoute.Input_Percent);
                            if (jobRoute == null)
                            {
                                throw new CustomException(Messages.ErrorGeneratingJobRoute);
                            }
                            jobRoutes.Add(jobRoute);
                        }
                    }

                    var operBomItems = _bomItemOperLinkRepository.Query().Where(x => x.Process_Id == input.ProcessId && x.Oper_Id == oper.Oper_Id && x.Parent_Item_Id == input.Item_Id.ToString() && x.Ver_Id == input.BomId).OrderBy(x => x.Mod_Id).ToArrayAsync();
                    if (operBomItems.Result.Count() > 0)
                    {
                        int bom_pos = 0;
                        foreach (var operBOMItem in operBomItems.Result)
                        {
                            // 基于工序中的BOM生成作业中的BOM
                            var jobBOM = new JobBom(input.Wo_Id, oper.Oper_Id, bom_pos, operBOMItem.Bom_Pos, operBOMItem.Qty_Per_Parent_Item, operBOMItem.Reqd_Start_Pct);
                            if (jobBOM == null)
                            {
                                throw new CustomException(Messages.ErrorGeneratingJobBOM);
                            }
                            jobBoms.Add(jobBOM);
                            bom_pos++;
                        }
                    }

                    // 从工序下工步中BOM获取的物料清单需依次按工步组、工步、物料在物料清单中的顺序排序，此排序会对line 216的判断结果产生影响，若没有按工步排序，生成作业下工步中的BOM时可能会抛出异常
                    var operStepBomItems = _bomItemOperStepLinkRepository.Query().Where(x => x.Process_Id == input.ProcessId && x.Oper_Id == oper.Oper_Id && x.Parent_Item_Id == input.Item_Id.ToString() && x.Ver_Id == input.BomId).OrderBy(x => x.Step_Grp_Id).ThenBy(x => x.Step_No).ThenBy(x => x.Mod_Id).ToArrayAsync();
                    if (operStepBomItems.Result.Count() > 0)
                    {
                        int bom_pos = 1;
                        int previousStepGrpId = -1;
                        int previousStepNo = -1;
                        foreach (var operStepBOMItem in operStepBomItems.Result)
                        {
                            // 工步下的物料均为消耗品，消耗品的下标从1开始；如果是同一个工步（即同一工步组下的同一工步），则工步中BOM下的物料的下标依次递增；若非同一工步，则工步中BOM下的物料下标从1开始
                            if (operStepBOMItem.Step_Grp_Id != previousStepGrpId || operStepBOMItem.Step_No != previousStepNo)
                            {
                                bom_pos = 1;
                            }
                            // 基于工序下工步中的BOM生成作业下工步中的BOM
                            var jobStepBOM = new JobBomStep(input.Wo_Id, oper.Oper_Id, bom_pos, operStepBOMItem.Step_Grp_Id, operStepBOMItem.Step_No, operStepBOMItem.Qty_Per_Parent_Item);
                            if (jobStepBOM == null)
                            {
                                throw new CustomException(Messages.ErrorGeneratingJobStepBOM);
                            }
                            jobStepBoms.Add(jobStepBOM);
                            previousStepGrpId = jobStepBOM.Step_Grp_Id;
                            previousStepNo = jobStepBOM.Step_No;
                            bom_pos++;
                        }
                    }
                }

                await _jobRepository.AddRangeAsync(jobs);
                await _jobStepGrpRepository.AddRangeAsync(jobStepGroups);
                await _jobStepRepository.AddRangeAsync(jobSteps);
                await _jobRouteRepository.AddRangeAsync(jobRoutes);
                await _jobBOMRepository.AddRangeAsync(jobBoms);
                await _jobStepBomRepository.AddRangeAsync(jobStepBoms);
            }

            // 基于工艺生成工单
            var workOrder = new Wo(input.ProcessId, input.Wo_Id, input.Wo_Desc, input.Item_Id, bom.Version, woStartQty, woReqQty, input.Wo_Priority, input?.Release_Time_Local,
                input?.Req_Finish_Time_Local, input?.Notes, 0);
            await _woRepository.AddAsync(workOrder);
            int status = await _woRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
        }

        /// <summary>
        /// 新增工单
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task AddWoAsync(AddWoInput input)
        {
            if (await _woRepository.ExistsAsync(x => x.Wo_Id == input.Wo_Id || x.Wo_Desc == input.Wo_Desc))
            {
                throw new CustomException(Messages.WoIdOrWoNameExist);
            }
            if (!await _materialRepository.ExistsAsync(x => x.Id == input.Item_Id && x.MaterialClassTypeName == _builtInTypes.BuiltInMaterialClassTypes[0]))
            {
                throw new CustomException(Messages.MaterialClassTypeNotProduction);
            }
            var wo = _mapper.Map<Wo>(input);
            wo.State_Cd = 0;
            await _woRepository.AddAsync(wo);
            var status = await _woRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
        }

        /// <summary>
        /// 编辑工单
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task UpdateWoAsync(UpdateWoInput input)
        {
            var wo = await _woRepository.FindAsync(x => x.Wo_Id == input.Wo_Id);
            if (wo == null)
            {
                throw new CustomException(Messages.WoNotFound);
            }
            var material = await _materialRepository.FindAsync(x => x.Id == input.Item_Id && x.MaterialClassTypeName == "成品");
            if (material == null)
            {
                throw new CustomException(Messages.MaterialClassTypeNotProduction);
            }
            if (input.Req_Qty > input.Start_Qty)
            {
                throw new CustomException(Messages.StartQtyLessReqQty);
            }
            input.Start_Qty = Math.Round(input.Start_Qty, material.PrecisionPlace, MidpointRounding.AwayFromZero);
            input.Req_Qty = Math.Round(input.Req_Qty, material.PrecisionPlace, MidpointRounding.AwayFromZero);

            var jobs = await _jobRepository.Query().Where(x => x.Wo_Id == input.Wo_Id).ToListAsync();
            if (jobs.Count == 0 && (input.Start_Qty > 0 || input.Req_Qty > 0))
            {
                throw new CustomException(Messages.WoHasNoJobNoStartQtyReqQty);
            }

            //处理开工数量
            if (jobs.Count > 0)
            {
                if (jobs.Any(x => x.Qty_At_Start > 0))//有作业的开工数量不为0
                {
                    var allJobQtyAtStart = jobs.Sum(x => x.Qty_At_Start);
                    double allButFinalQtyAtStart = 0;
                    for (int i = 0; i < jobs.Count; i++)
                    {
                        if (i < jobs.Count - 1)
                        {
                            var currentQtyAtStart = Math.Round((jobs[i].Qty_At_Start / allJobQtyAtStart) * input.Start_Qty, material.PrecisionPlace, MidpointRounding.AwayFromZero);
                            jobs[i].Qty_At_Start = currentQtyAtStart;
                            allButFinalQtyAtStart += currentQtyAtStart;
                        }
                        else
                        {
                            jobs[i].Qty_At_Start = input.Start_Qty - allButFinalQtyAtStart;
                        }
                    }
                    wo.Start_Qty = input.Start_Qty;
                }
                else //所有的作业的开工数量都为0
                {
                    foreach (var item in jobs)
                    {
                        item.Qty_At_Start = input.Start_Qty;//作业的开工数量是输入的工单的开工数量
                    }
                    wo.Start_Qty = input.Start_Qty * jobs.Count;//工单的开工数量是所有作业的开工数量的总和
                }
            }

            //处理所需产量
            if (jobs.Count > 0)
            {
                if (jobs.Any(x => x.Qty_Reqd > 0))//有作业的所需产量不为0
                {
                    var allJobQtyReqd = jobs.Sum(x => x.Qty_Reqd);
                    double allButFinalQtyAtReqd = 0;
                    for (int i = 0; i < jobs.Count; i++)
                    {
                        if (i < jobs.Count - 1)
                        {
                            var currentQtyReqd = Math.Round((jobs[i].Qty_Reqd / allJobQtyReqd) * input.Req_Qty, material.PrecisionPlace, MidpointRounding.AwayFromZero);
                            jobs[i].Qty_Reqd = currentQtyReqd;
                            allButFinalQtyAtReqd += currentQtyReqd;
                        }
                        else
                        {
                            jobs[i].Qty_Reqd = input.Req_Qty - allButFinalQtyAtReqd;
                        }
                    }
                    wo.Req_Qty = input.Req_Qty;
                }
                else //所有的作业的所需产量都为0
                {
                    foreach (var item in jobs)
                    {
                        item.Qty_Reqd = input.Req_Qty;//作业的所需产量是输入的工单的所需产量
                    }
                    wo.Req_Qty = Math.Round(input.Req_Qty * jobs.Count, material.PrecisionPlace, MidpointRounding.AwayFromZero);//工单的所需产量是所有作业的所需产量的总和
                }
            }

            wo.Item_Id = input.Item_Id;
            wo.Release_Time_Local = input.Release_Time_Local;
            wo.Req_Finish_Time_Local = input.Req_Finish_Time_Local;
            wo.Wo_Priority = input.Wo_Priority;
            wo.Notes = input.Notes;
            var status = await _woRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
        }

        /// <summary>
        /// 删除工单
        /// </summary>
        /// <param name="wo_Id">工单Id</param>
        /// <returns></returns>
        public async Task DeleteWoAsync(string wo_id)
        {
            _woRepository.Delete(x => x.Wo_Id == wo_id);
            _jobRepository.Delete(x => x.Wo_Id == wo_id);
            _jobBOMRepository.Delete(x => x.Wo_Id == wo_id);
            _jobRouteRepository.Delete(x => x.Wo_Id == wo_id);
            _jobStepGrpRepository.Delete(x => x.Wo_Id == wo_id);
            _jobStepRepository.Delete(x => x.Wo_Id == wo_id);
            _jobStepBomRepository.Delete(x => x.Wo_Id == wo_id);
            var status = await _woRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
        }

        /// <summary>
        /// 分页查询工单列表
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<PagedItemsData<WosResult>> GetWosAsync(GetWosInput input)
        {
            Expression<Func<Wo, bool>> filter = x => true;
            if (!string.IsNullOrEmpty(input.KeyWord))
            {
                filter = filter.And(x => x.Wo_Id.Contains(input.KeyWord) || x.Wo_Desc.Contains(input.KeyWord));
            }
            var data = await _woRepository.Query().Where(filter).OrderByDescending(x => x.Row_Id).ToPagedAsync(input.PagedInput);
            var result = _mapper.Map<PagedItemsData<WosResult>>(data);
            var materialIds = result.Items.Select(x => x.Item_Id).Distinct().ToList();
            var materials = await _materialRepository.Query().Where(x => materialIds.Contains(x.Id)).ToListAsync();
            foreach (var item in result.Items)
            {
                item.Item_Name = materials.FirstOrDefault(x => x.Id == item.Item_Id)?.Name ?? String.Empty;
            }

            var woIds = result.Items.Select(x => x.Wo_Id).ToList();
            var jobs = await _jobRepository.Query().Where(x => woIds.Contains(x.Wo_Id)).ToListAsync();
            var jobMaterialIds = jobs.Select(x => x.Item_Id).Distinct().ToList();
            var jobMaterials = await _materialRepository.Query().Where(x => jobMaterialIds.Contains(x.Id)).ToListAsync();
            var initEntIds = jobs.Select(x => x.Init_Sched_Ent_Id).Distinct().ToList();
            var targetEntIds = jobs.Select(x => x.Target_Sched_Ent_Id).Distinct().ToList();
            var allEntIds = initEntIds.Union(targetEntIds);
            var ents = await _productionUnitRepository.Query().Where(x => allEntIds.Contains(x.Id)).ToListAsync();
            var jobStateIds = jobs.Select(x => x.State_Cd).Distinct().ToList();
            var jobstates = await _job_StateRepository.Query().Where(x => jobStateIds.Contains(x.State_Cd)).ToListAsync();

            var jobRoutes = await _jobRouteRepository.Query().Where(x => woIds.Contains(x.Wo_Id)).ToListAsync();
            foreach (var item in result.Items)
            {
                var currentJobs = jobs.Where(x => x.Wo_Id == item.Wo_Id).ToList();
                var currentJobResults = new List<JobResult>();
                var currentjobRoutes = jobRoutes.Where(x => x.Wo_Id == item.Wo_Id).ToList();
                var firstjob = currentJobs.Where(x => x.First_Job == true).FirstOrDefault();
                var currentJob = firstjob;
                for (int i = 0; i < currentJobs.Count; i++)
                {
                    var currentJobResult = _mapper.Map<JobResult>(currentJob);
                    currentJobResult.Item_Name = jobMaterials.FirstOrDefault(x => x.Id == currentJobResult.Item_Id)?.Name ?? String.Empty;
                    currentJobResult.Init_Sched_Ent_Name = ents.FirstOrDefault(x => x.Id == currentJobResult.Init_Sched_Ent_Id)?.ProductionUnitName ?? String.Empty;
                    currentJobResult.Target_Sched_Ent_Name = ents.FirstOrDefault(x => x.Id == currentJobResult.Target_Sched_Ent_Id)?.ProductionUnitName ?? String.Empty;
                    currentJobResult.State_Desc = jobstates.FirstOrDefault(x => x.State_Cd == currentJobResult.State_Cd)?.State_Desc ?? String.Empty;
                    currentJobResults.Add(currentJobResult);
                    if (currentJob.Final_Job == false)
                    {
                        var nextJobId = currentjobRoutes.Where(x => x.Input_Oper_Id == currentJob.Oper_Id).FirstOrDefault().Oper_Id;
                        var nextJob = currentJobs.Where(x => x.Oper_Id == nextJobId).FirstOrDefault();
                        currentJob = nextJob;
                    }
                }
                item.JobResults = currentJobResults;
            }
            return result;
        }

        /// <summary>
        /// 查询工单实体
        /// </summary>
        /// <param name="wo_Id"></param>
        /// <returns></returns>
        public async Task<WoResult> GetWoAsync(string wo_id)
        {
            var wo = await _woRepository.FindAsync(x => x.Wo_Id == wo_id);
            if (wo == null)
            {
                throw new CustomException(Messages.WoNotFound);
            }
            var data = _mapper.Map<WoResult>(wo);
            var material = await _materialRepository.FindAsync(x => x.Id == data.Item_Id);
            data.Item_Name = material.Name;
            return data;
        }

        /// <summary>
        /// 查询所有工艺的物料和物料清单
        /// </summary>
        /// <returns></returns>
        public async Task<ItemsResult<ProcessForWoResult>> GetAllProcessesMaterialMaterialBOMAsync()
        {
            var processes = await _processRepository.Query().ToListAsync();
            var data = _mapper.Map<List<ProcessForWoResult>>(processes);
            var processIds = data.Select(x => x.Process_Id).ToList();
            var links = await _itemProcessLinkRepository.Query().Where(x => processIds.Contains(x.Process_Id)).ToListAsync();
            var materialIds = links.Select(x => x.MaterialId).Distinct().ToList();
            var materials = await _materialRepository.Query().Where(x => materialIds.Contains(x.Id)).ToListAsync();
            var materiallinks = await _materialMaterialBOMLinkRepository.Query().Where(x => materialIds.Contains(x.MaterialId)).ToListAsync();
            var bomIds = materiallinks.Select(x => x.MaterialBOMId).Distinct().ToList();
            var boms = await _materialBOMRepository.Query().Where(x => bomIds.Contains(x.Id)).ToListAsync();
            foreach (var item in data)
            {
                var currentLink = links.Where(x => x.Process_Id == item.Process_Id).FirstOrDefault();
                if (currentLink != null)
                {
                    var currentMaterial = materials.Where(x => x.Id == currentLink.MaterialId).FirstOrDefault();
                    if (currentMaterial != null)
                    {
                        item.Material = _mapper.Map<MaterialForWoResult>(currentMaterial);
                        var currentMaterialLinks = materiallinks.Where(x => x.MaterialId == currentLink.MaterialId).Distinct().ToList();
                        var currentBomIds = currentMaterialLinks.Select(x => x.MaterialBOMId).Distinct().ToList();
                        var currentBoms = boms.Where(x => currentBomIds.Contains(x.Id)).ToList();
                        item.Material.MaterialBOMs = _mapper.Map<List<IdNameResult>>(currentBoms);
                    }
                }
            }
            var result = new ItemsResult<ProcessForWoResult> { Items = data };
            return result;
        }

        /// <summary>
        /// 查询工艺关联的待生产物料，以及待生产物料对应的BOM和BOM下的物料列表
        /// </summary>
        /// <param name="processId"></param>
        /// <returns></returns>
        public async Task<MaterialBOMResults> GetProcessMaterialBOMAsync(string processId)
        {
            var process = _processRepository.Find(x => x.Process_Id == processId);
            if (process == null)
            {
                throw new CustomException(Messages.ProcessNotFound);
            }

            var materialProcessLinks = _itemProcessLinkRepository.Query().Where(x => x.Process_Id == processId).ToList();
            if (materialProcessLinks == null || materialProcessLinks.Count == 0)
            {
                throw new CustomException(Messages.MaterialNotFound);
            }
            else if (materialProcessLinks.Count == 1)
            {
                var materialId = materialProcessLinks[0].MaterialId;
                var material = _materialRepository.Find(x => x.Id == materialId);
                if (material == null)
                {
                    throw new CustomException(Messages.MaterialNotFound);
                }

                var materialBOMLinks = _materialMaterialBOMLinkRepository.Query().Where(x => x.MaterialId == materialProcessLinks.First().MaterialId).ToList();
                if (materialBOMLinks == null || materialBOMLinks.Count == 0)
                {
                    throw new CustomException(Messages.MaterialBOMNotFound);
                }
                var bomIds = materialBOMLinks.Select(x => x.MaterialBOMId).ToList();
                if (bomIds == null || bomIds.Count() == 0)
                {
                    throw new CustomException(Messages.MaterialBOMNotFound);
                }
                IList<IdNameResult> bomInfo = new List<IdNameResult>();
                foreach (var item in bomIds)
                {
                    var bom = _materialBOMRepository.Find(x => x.Id == item);
                    if (bom == null)
                    {
                        throw new CustomException(Messages.MaterialBOMNotFound);
                    }
                    IdNameResult bomIdName = new IdNameResult(bom.Id, bom.Name);
                    bomInfo.Add(bomIdName);
                }

                MaterialBOMResults woMaterialMaterialBOMResult = new MaterialBOMResults(material.Id, material.Name, material.MeasuringUnitId, material.MeasuringUnitName, bomInfo);
                if (woMaterialMaterialBOMResult == null)
                {
                    throw new CustomException(Messages.MaterialBOMNotFound);
                }
                return woMaterialMaterialBOMResult;
            }
            else
            {
                throw new CustomException(Messages.CurrentVersionNotSupport);
            }
        }

        /// <summary>
        /// 移动作业的位置
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task MovingJobSequenceAsync(MovingJobSequenceInput input)
        {
            if (!await _woRepository.ExistsAsync(x => x.Wo_Id == input.Wo_Id))
            {
                throw new CustomException(Messages.WoNotFound);
            }
            if (input.FromOper_Id == input.ToOper_Id)
            {
                throw new CustomException(Messages.SrcOperMustNotEqualDestOper);
            }
            var allOperIds = new List<string>() { input.FromOper_Id, input.ToOper_Id };
            var opers = await _jobRepository.Query().Where(x => allOperIds.Contains(x.Oper_Id) && x.Wo_Id == input.Wo_Id).ToListAsync();
            if (opers.Count != 2)
            {
                throw new CustomException(Messages.SomeOperNotExist);
            }
            if (!await _jobRouteRepository.ExistsAsync(x => x.Input_Oper_Id == input.ToOper_Id && x.Oper_Id == input.FromOper_Id && x.Wo_Id == input.Wo_Id))
            {
                throw new CustomException(Messages.CurrentOpersCantBeMoved);
            }
            var FromOper = opers.FirstOrDefault(x => x.Oper_Id == input.FromOper_Id && x.Wo_Id == input.Wo_Id);
            var ToOper = opers.FirstOrDefault(x => x.Oper_Id == input.ToOper_Id && x.Wo_Id == input.Wo_Id);
            if (ToOper.First_Job == true && FromOper.Final_Job == false)
            {
                var currentRoute = await _jobRouteRepository.FindAsync(x => x.Input_Oper_Id == input.ToOper_Id && x.Oper_Id == input.FromOper_Id && x.Wo_Id == input.Wo_Id);
                var nextRoute = await _jobRouteRepository.FindAsync(x => x.Input_Oper_Id == input.FromOper_Id && x.Wo_Id == input.Wo_Id);
                var newCurrentRoute = new JobRoute
                {
                    Input_Oper_Id = currentRoute.Oper_Id,
                    Oper_Id = currentRoute.Input_Oper_Id,
                    Wo_Id = input.Wo_Id
                };
                var nextCurrentRoute = new JobRoute
                {
                    Input_Oper_Id = currentRoute.Input_Oper_Id,
                    Oper_Id = nextRoute.Oper_Id,
                    Wo_Id = input.Wo_Id
                };

                _jobRouteRepository.Delete(currentRoute);
                _jobRouteRepository.Delete(nextRoute);
                int status = await _jobRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
                await _jobRouteRepository.AddAsync(newCurrentRoute);
                await _jobRouteRepository.AddAsync(nextCurrentRoute);
                FromOper.First_Job = true;
                ToOper.First_Job = false;
                status = await _jobRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
            }
            else if (FromOper.Final_Job == true && ToOper.First_Job == false)
            {
                var currentRoute = await _jobRouteRepository.FindAsync(x => x.Input_Oper_Id == input.ToOper_Id && x.Oper_Id == input.FromOper_Id && x.Wo_Id == input.Wo_Id);
                var privousRoute = await _jobRouteRepository.FindAsync(x => x.Oper_Id == input.ToOper_Id && x.Wo_Id == input.Wo_Id);
                var newCurrentRoute = new JobRoute
                {
                    Input_Oper_Id = currentRoute.Oper_Id,
                    Oper_Id = currentRoute.Input_Oper_Id,
                    Wo_Id = input.Wo_Id
                };
                var privousCurrentRoute = new JobRoute
                {
                    Input_Oper_Id = privousRoute.Input_Oper_Id,
                    Oper_Id = currentRoute.Oper_Id,
                    Wo_Id = input.Wo_Id
                };
                _jobRouteRepository.Delete(currentRoute);
                _jobRouteRepository.Delete(privousRoute);
                int status = await _jobRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
                await _jobRouteRepository.AddAsync(newCurrentRoute);
                await _jobRouteRepository.AddAsync(privousCurrentRoute);
                FromOper.Final_Job = false;
                ToOper.Final_Job = true;
                status = await _jobRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
            }
            else if (FromOper.Final_Job == true && ToOper.First_Job == true)
            {
                var currentRoute = await _jobRouteRepository.FindAsync(x => x.Input_Oper_Id == input.ToOper_Id && x.Oper_Id == input.FromOper_Id && x.Wo_Id == input.Wo_Id);
                var newCurrentRoute = new JobRoute
                {
                    Input_Oper_Id = currentRoute.Oper_Id,
                    Oper_Id = currentRoute.Input_Oper_Id,
                    Wo_Id = input.Wo_Id
                };
                FromOper.First_Job = true;
                FromOper.Final_Job = false;
                ToOper.First_Job = false;
                ToOper.Final_Job = true;
                _jobRouteRepository.Delete(currentRoute);
                await _jobRouteRepository.AddAsync(newCurrentRoute);
                var status = await _jobRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
            }
            else
            {
                // 没有firstoper和finaloper
                var currentRoute = await _jobRouteRepository.FindAsync(x => x.Input_Oper_Id == input.ToOper_Id && x.Oper_Id == input.FromOper_Id && x.Wo_Id == input.Wo_Id);
                var privousRoute = await _jobRouteRepository.FindAsync(x => x.Oper_Id == input.ToOper_Id && x.Wo_Id == input.Wo_Id);
                var nextRoute = await _jobRouteRepository.FindAsync(x => x.Input_Oper_Id == input.FromOper_Id && x.Wo_Id == input.Wo_Id);

                var newCurrentRoute = new JobRoute
                {
                    Input_Oper_Id = currentRoute.Oper_Id,
                    Oper_Id = currentRoute.Input_Oper_Id,
                    Wo_Id = input.Wo_Id
                };

                var privousCurrentRoute = new JobRoute
                {
                    Input_Oper_Id = privousRoute.Input_Oper_Id,
                    Oper_Id = currentRoute.Oper_Id,
                    Wo_Id = input.Wo_Id
                };

                var nextCurrentRoute = new JobRoute
                {
                    Input_Oper_Id = currentRoute.Input_Oper_Id,
                    Oper_Id = nextRoute.Oper_Id,
                    Wo_Id = input.Wo_Id
                };

                _jobRouteRepository.Delete(currentRoute);
                _jobRouteRepository.Delete(privousRoute);
                _jobRouteRepository.Delete(nextRoute);
                int status = _jobRouteRepository.Save();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
                await _jobRouteRepository.AddAsync(newCurrentRoute);
                await _jobRouteRepository.AddAsync(privousCurrentRoute);
                await _jobRouteRepository.AddAsync(nextCurrentRoute);
                status = await _jobRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
            }
        }

        /// <summary>
        /// 查询某个工单的路线信息，按顺序显示作业的操作编号
        /// </summary>
        /// <param name="wo_Id">工单编号</param>
        /// <returns></returns>
        public async Task<ItemsResult<WoJobRoutesResult>> GetWoJobRoutesAsync(string wo_Id)
        {
            if (!await _woRepository.ExistsAsync(x => x.Wo_Id == wo_Id))
            {
                throw new CustomException(Messages.WoNotFound);
            }
            var jobs = await _jobRepository.Query().Where(x => x.Wo_Id == wo_Id).ToListAsync();
            var jobRoutes = await _jobRouteRepository.Query().Where(x => x.Wo_Id == wo_Id).ToListAsync();
            var result = new List<WoJobRoutesResult>();
            var firstjob = jobs.Where(x => x.First_Job == true).FirstOrDefault();
            var currentJob = firstjob;
            for (int i = 0; i < jobs.Count; i++)
            {
                var currentJobResult = _mapper.Map<WoJobRoutesResult>(currentJob);
                result.Add(currentJobResult);
                if (currentJob.Final_Job == false)
                {
                    var nextJobId = jobRoutes.Where(x => x.Input_Oper_Id == currentJob.Oper_Id).FirstOrDefault().Oper_Id;
                    var nextJob = jobs.Where(x => x.Oper_Id == nextJobId).FirstOrDefault();
                    currentJob = nextJob;
                }
            }
            return new ItemsResult<WoJobRoutesResult> { Items = result };
        }


        private bool IsInputBomValid(string processId, int bomId)
        {
            Task<MaterialBOMResults> result = GetProcessMaterialBOMAsync(processId);
            if (result == null)
            {
                throw new CustomException(Messages.MaterialBOMNotFound);
            }
            var bomIds = result.Result.MaterialBOMs.Select(x => x.Id);
            if (!bomIds.Contains(bomId))
            {
                throw new CustomException(Messages.MaterialBOMNotFound);
            }
            var bom = _materialBOMRepository.Find(x => x.Id == bomId);
            if (bom == null)
            {
                throw new CustomException(Messages.MaterialBOMNotFound);
            }
            return true;
        }
    }
}
